/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.solr.search;

import java.util.Collection;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.solr.SolrTestCaseJ4;
import org.junit.BeforeClass;

/** */
public class TestQueryUtils extends SolrTestCaseJ4 {

  @BeforeClass
  public static void beforeClass() throws Exception {
    initCore("solrconfig.xml", "schema.xml");
  }

  @Override
  public void setUp() throws Exception {
    super.setUp();
  }

  @Override
  public void tearDown() throws Exception {
    super.tearDown();
  }

  public void positive(Query q) {
    assertFalse(QueryUtils.isNegative(q));
    assertSame(QueryUtils.getAbs(q), q);
    Collection<BooleanClause> clauses =
        (q instanceof BooleanQuery) ? ((BooleanQuery) q).clauses() : null;
    if (clauses != null) {
      if (clauses.size() != 0) {
        assertSame(QueryUtils.makeQueryable(q), q);
      }
    } else {
      assertSame(QueryUtils.makeQueryable(q), q);
    }
  }

  public void negative(Query q) {
    assertTrue(QueryUtils.isNegative(q));
    Query abs = QueryUtils.getAbs(q);
    assertNotSame(q, abs);
    Query neg2 = QueryUtils.fixNegativeQuery(q);

    assertNotEquals(abs, q);
    assertNotEquals(neg2, q);
  }

  public void testNegativeQueries() {
    TermQuery tq = new TermQuery(new Term("hi", "there"));
    TermQuery tq2 = new TermQuery(new Term("wow", "dude"));
    BooleanQuery.Builder bq = new BooleanQuery.Builder();

    positive(tq);
    // positive(bq);
    bq.add(tq, BooleanClause.Occur.SHOULD);
    positive(bq.build());
    bq.add(tq2, BooleanClause.Occur.MUST_NOT);
    positive(bq.build());

    bq = new BooleanQuery.Builder();
    bq.add(tq, BooleanClause.Occur.MUST_NOT);
    negative(bq.build());

    bq.add(tq2, BooleanClause.Occur.MUST_NOT);
    negative(bq.build());

    String f = "name"; // name is whitespace tokenized

    assertU(adoc("id", "1", f, "A"));
    assertU(adoc("id", "2", f, "B"));
    assertU(adoc("id", "3", f, "C"));
    assertU(adoc("id", "4", f, "C"));
    assertU(adoc("id", "5", f, "D"));
    assertU(adoc("id", "6", f, "E"));
    assertU(adoc("id", "7", f, "E"));
    assertU(adoc("id", "8", f, "E W"));
    assertU(adoc("id", "9", f, "F W"));
    assertU(adoc("id", "10", f, "G W"));
    assertU(adoc("id", "11", f, "G X "));
    assertU(adoc("id", "12", f, "G X Y"));
    assertU(adoc("id", "13", f, "G X Y Z"));
    assertU(adoc("id", "14", f, "G Y Z"));
    assertU(adoc("id", "15", f, "G Z"));
    assertU(adoc("id", "16", f, "G"));
    assertU(commit());

    assertQ(
        "test negative base q matching nothing",
        req("-qlkciyopsbgzyvkylsjhchghjrdf"),
        "//result[@numFound='16']");

    assertQ("test negative base q matching something", req("-name:E"), "//result[@numFound='13']");

    assertQ(
        "test negative base q with two terms", req("-name:G -name:W"), "//result[@numFound='7']");

    assertQ(
        "test negative base q with three terms",
        req("-name:G -name:W -name:E"),
        "//result[@numFound='5']");

    assertQ("test negative boolean query", req("-(name:G OR name:W)"), "//result[@numFound='7']");

    assertQ(
        "test non negative q",
        req("-name:G -name:W -name:E id:[* TO *]"),
        "//result[@numFound='5']");

    assertQ(
        "test non negative q",
        req("-name:G -name:W -name:E +id:[* TO *]"),
        "//result[@numFound='5']");

    // now for the filters...
    assertQ(
        "test negative base q matching nothing, with filters",
        req("q", "-qlkciyopsbgzyvkylsjhchghjrdf", "fq", "name:A"),
        "//result[@numFound='1']");

    assertQ(
        "test negative filters", req("q", "name:A", "fq", "-name:A"), "//result[@numFound='0']");
    assertQ(
        "test negative filters", req("q", "name:A", "fq", "-name:A"), "//result[@numFound='0']");
    assertQ(
        "test negative filters", req("q", "-name:E", "fq", "name:E"), "//result[@numFound='0']");
    assertQ(
        "test negative filters", req("q", "-name:E", "fq", "name:W"), "//result[@numFound='2']");
    assertQ(
        "test negative filters", req("q", "-name:E", "fq", "name:W"), "//result[@numFound='2']");
    assertQ(
        "one pos filter, one neg",
        req("q", "-name:E", "fq", "name:W", "fq", "-name:G"),
        "//result[@numFound='1']");
    assertQ(
        "two neg filters",
        req("q", "-name:E", "fq", "-name:W", "fq", "-name:G"),
        "//result[@numFound='5']" // ABCCD
        );

    assertQ(
        "three neg filters",
        req("q", "-name:E", "fq", "-name:W", "fq", "-name:G", "fq", "-name:C"),
        "//result[@numFound='3']" // ABD
        );

    assertQ(
        "compound neg filters",
        req("q", "-name:E", "fq", "-name:W -name:G", "fq", "-name:C"),
        "//result[@numFound='3']" // ABD
        );

    assertQ(
        "compound neg filters",
        req("q", "-name:E", "fq", "-name:W -name:G -name:C"),
        "//result[@numFound='3']" // ABD
        );

    assertQ(
        "compound neg filters",
        req("q", "-name:E", "fq", "-(name:W name:G name:C)"),
        "//result[@numFound='3']" // ABD
        );

    assertQ(
        "three neg filters + pos",
        req("q", "-name:E", "fq", "-name:W", "fq", "-name:G", "fq", "-name:C", "fq", "name:G"),
        "//result[@numFound='0']");
    assertQ(
        "three neg filters + pos",
        req("q", "-name:E", "fq", "-name:W", "fq", "-name:G", "fq", "-name:C", "fq", "+id:1"),
        "//result[@numFound='1']" // A
        );
    assertQ(
        "three neg filters + pos",
        req("q", "-name:E", "fq", "-name:W", "fq", "-name:G", "fq", "-name:C", "fq", "id:[* TO *]"),
        "//result[@numFound='3']" // ABD
        );

    // QueryParser turns term queries on stopwords into a BooleanQuery with
    // zero clauses.
    assertQ(
        "neg base query on stopword", req("q", "-text:stopworda"), "//result[@numFound='16']" // ABD
        );

    assertQ(
        "negative filter on stopword",
        req("q", "id:[* TO *]", "fq", "-text:stopworda"),
        "//result[@numFound='16']" // ABD
        );
    assertQ(
        "two negative filters on stopword",
        req("q", "id:[* TO *]", "fq", "-text:stopworda", "fq", "-text:stopworda"),
        "//result[@numFound='16']" // ABD
        );
    assertQ(
        "compound negative filters with stopword",
        req("q", "id:[* TO *]", "fq", "-text:stopworda -id:1"),
        "//result[@numFound='15']" // ABD
        );
  }
}
